/****************************************************************************
* MINILOGO - Minimalistic Logo Interpreter
* Copyright (C) 1999-2008 Willian Mitsuda
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
* 
*****************************************************************************

*****************************************************************************
* SHELL.C                                                                   *
*****************************************************************************
* Contem funcoes de acesso e interacao com o shell do MINI-LOGO             *
****************************************************************************/
#include <stdlib.h>  // Necessario para atof()
#include <string.h>  // Funcoes de manipulacao de strings
#include <grx20.h>   // Funcoes graficas
#include <grxkeys.h> // Codigos de teclas
#include "defines.h" // Definicoes do projeto

// Defines locais...
#define MAX_SHELL 255 // Comprimento maximo de uma linha de comando
#define MAX_HIST 20   // Comprimento maximo do historico do shell
#define SPROMPT ">"   // Prompt do shell
#define MAX_ARG 5     // Numero maximo de argumentos que um comando aceita

// Tipo de argumento identifica por nextarg()
typedef enum
{
  A_comando, // O argumento eh um comando
  A_inteiro, // O argumento eh um numero inteiro
  A_bloco,   // O argumento eh um bloco de comandos
  A_fim,     // Fim da string de comandos
  A_erro,    // ERRO!!! Argumento invalido
} arg_t;

// Estrutura que representa uma linha na tabela de descricao de comandos
typedef struct
{
  char nome[MAX_SHELL + 1];   // Nome do comando
  cmd_stat (*func)(char *[]); // Ponteiro p/ funcao tratadora
  unsigned char nargs;        // Numero de argumentos aceitos
  arg_t targs[MAX_ARG];       // Array com os tipos de argumentos aceitos
} cmd_descr;

// Importa a tabela de descricao de comandos
#include "cmdtab.h"

// Variaveis de controle do shell
static int l = MINSY, c = MINSX;
static int ins = 1;
static int pos, tam;

/****************************************************************************
* static GrTextOption fontsh(GrColor fg, GrColor bg)                        *
*****************************************************************************
* Constroi um GrTextOption de acordo com as cores especificadas             *
*****************************************************************************
* Parametros:                                                               *
*   GrColor fg - A cor do texto                                             *
*   GrColor bg - A cor de fundo                                             *
*****************************************************************************
* Retorna:                                                                  *
*   O GrTextOption personalizado                                            *
****************************************************************************/
static GrTextOption fontsh(GrColor fg, GrColor bg)
{
  return((GrTextOption) {&GrFont_PC8x16,
                         {fg},
                         {bg},
                         GR_BYTE_TEXT,
                         GR_TEXT_RIGHT,
                         GR_ALIGN_LEFT,
                         GR_ALIGN_TOP});
}

/****************************************************************************
* void clsh(void)                                                           *
*****************************************************************************
* Limpa a janela do shell                                                   *
****************************************************************************/
void clsh(void)
{
  // Desenha uma caixa preenchida com SBG, nas coordenadas (MINSX, MINSY)
  // ate (MAXSX, MAXSY) e posiciona o cursor no inicio da janela...
  GrFilledBox(MINSX, MINSY, MAXSX, MAXSY, SBG);
  l = MINSY;
  c = MINSX;
}

/****************************************************************************
* int newline(void)                                                         *
*****************************************************************************
* Abre uma nova linha no shell                                              *
*****************************************************************************
* Retorna:                                                                  *
*   Um valor indicando se houver "scroll" do shell                          *
****************************************************************************/
int newline(void)
{
  // Posiciona o cursor na primeira coluna da janela do shell...
  c = MINSX;

  // Se a proxima linha ultrapassar o limite inferior da janela
  // do shell, executa um "scroll" na janela...
  // ... caso contrario, simplesmente avanca a linha...
  if(l + 2 * CURSH - 1 > MAXSY)
  {
    // Captura o contexto da tela...
    GrContext *ct = (GrContext *) GrScreenContext();

    // Executa o scroll...
    GrBitBlt(ct, MINSX, MINSY, ct, MINSX, MINSY + CURSH, MAXSX, MAXSY, GrWRITE);
    GrFilledBox(MINSX, MINSY + CURSH * ((MAXSY - MINSY + 1) / CURSH - 1), MAXSX, MAXSY, SBG);
    return(1);
  }
  else
    l += CURSH;
  return(0);
}

/****************************************************************************
* static void rtcurs(void)                                                  *
*****************************************************************************
* Retorna o cursor uma coluna                                               *
****************************************************************************/
static void rtcurs(void)
{
  if(c - CURSW < MINSX)
  {
    l -= CURSH;
    c = MINSX + CURSW * (((MAXSX - MINSX + 1) / CURSW) - 1);
  }
  else
    c -= CURSW;
}

/****************************************************************************
* static int avcurs(void)                                                   *
*****************************************************************************
* Avanca o cursor uma coluna                                                *
*****************************************************************************
* Retorna:                                                                  *
*   Um valor indicando se houve "scroll" ou nao                             *
****************************************************************************/
static int avcurs(void)
{
  if(c + 2 * CURSW - 1 > MAXSX)
  {
    c = MINSX;
    return(newline());
  }
  else
    c += CURSW;
  return(0);
}

/****************************************************************************
* static int printchar(int ch, int avanca, GrColor fg)                      *
*****************************************************************************
* Imprime um caracter no shell                                              *
*****************************************************************************
* Parametros:                                                               *
*   int ch     - Caracter a ser impresso                                    *
*   int avanca - Avancar o cursor?                                          *
*   GrColor fg - A cor do texto                                             *
*****************************************************************************
* Retorna:                                                                  *
*   Um valor indicando se houve "scroll" ou nao                             *
****************************************************************************/
static int printchar(int ch, int avanca, GrColor fg)
{
  GrTextOption font = fontsh(fg, SBG);
  GrDrawChar(ch, c, l, &font);
  if(avanca)
    return(avcurs());
  return(0);
}

/****************************************************************************
* static void printshell(char *str, int avanca, GrColor fg)                 *
*****************************************************************************
* Imprime um string no shell                                                *
*****************************************************************************
* Parametros:                                                               *
*   char *str  - A string a ser impressa                                    *
*   int avanca - Avancar o cursor?                                          *
*   GrColor fg - A cor do texto                                             *
****************************************************************************/
static void printshell(char *str, int avanca, GrColor fg)
{
  int ol = l, oc = c, scount = 0;
  while(*str)
    if(printchar(*str++, 1, fg))
      scount++;
  if(!avanca)
  {
    l = ol - scount * CURSH;
    c = oc;
  }
}

/****************************************************************************
* void printstr(char *str, GrColor fg)                                      *
*****************************************************************************
* Imprime uma string no shell                                               *
*****************************************************************************
* Parametros:                                                               *
*   char *str  - A string a ser impressa                                    *
*   GrColor fg - A cor do texto                                             *
****************************************************************************/
void printstr(char *str, GrColor fg)
{
  printshell(str, 1, fg);
}

/****************************************************************************
* void printline(char *str, GrColor fg)                                     *
*****************************************************************************
* Imprime uma string seguida de uma nova linha no shell                     *
*****************************************************************************
* Parametros:                                                               *
*   char *str  - A string a ser impressa                                    *
*   GrColor fg - A cor do texto                                             *
****************************************************************************/
void printline(char *str, GrColor fg)
{
  printstr(str, fg);
  newline();
}

/****************************************************************************
* static void clear(void)                                                   *
*****************************************************************************
* Limpa a linha de comando                                                  *
****************************************************************************/
static void clear(void)
{
  char vazio[MAX_SHELL + 1];
  int i;
  for(i = 0; i < pos; i++)
    rtcurs();
  for(i = 0; i < tam; i++)
    vazio[i] = ' ';
  vazio[tam] = 0;
  printshell(vazio, 0, SFG);
  pos = tam = 0;
}

/****************************************************************************
* static void showcursor(void)                                              *
*****************************************************************************
* Exibe/Esconde o cursor                                                    *
****************************************************************************/
static void showcursor(void)
{
  if(ins)
    GrFilledBox(c, l + CURSH - 2, c + CURSW - 1, l + CURSH - 1, SCS | GrXOR);
  else
    GrFilledBox(c, l, c + CURSW - 1, l + CURSH - 1, SCS | GrXOR);
}

/****************************************************************************
* static void strrem(char *string)                                          *
*****************************************************************************
* Remove o primeiro e o ultimo caracter de uma string                       *
*****************************************************************************
* Parametros:                                                               *
*   char *string - Endereco do inicio da string                             *
****************************************************************************/
static void strrem(char *string)
{
  // Se o tamanho da string for menor que 3, apaga toda a string...
  if(strlen(string) < 3)
    *string = 0;
  else
  {
    char *pos = string;
    while(*(pos + 2) != 0) {
      *pos = *(pos + 1);
      pos++;
    }
    *pos = 0;
  }
}

/****************************************************************************
* static arg_t nextarg(char **pos, char *buffer)                            *
*****************************************************************************
* Identifica o proximo argumento de uma linha de comando                    *
*****************************************************************************
* Parametros:                                                               *
*   char **pos   - Endereco de um ponteiro char que sera movimentado        *
*   char *buffer - Endereco do buffer onde o token sera copiado             *
*****************************************************************************
* Retorna:                                                                  *
*   Um valor do tipo arg_t indicando o tipo de argumento identificado e     *
* copiado para o buffer                                                     *
****************************************************************************/
static arg_t nextarg(char **pos, char *buffer)
{
  char *ibuffer = buffer; // Inicio do buffer de saida
  arg_t ret;              // Variavel de retorno

  // Ignora espacos em branco...
  while(**pos == ' ') (*pos)++;

  // Atualiza a variavel de retorno:
  // Verifica se o tipo de argumento eh um...
  // ...comando:
  // Verifica se o primeiro caracter eh uma letra...
  if((**pos >= 'a' && **pos <= 'z') ||
     (**pos >= 'A' && **pos <= 'Z'))
  {
    ret = A_comando; // Tipo = comando...

    // Transfere os bytes para o buffer enquanto for letra ou digito...
    while((**pos >= 'a' && **pos <= 'z') ||
          (**pos >= 'A' && **pos <= 'Z') ||
          (**pos >= '0' && **pos <= '9')) *buffer++ = *(*pos)++;

    // Se o proximo caracter depois do comando nao for o inicio de
    // um bloco ou espaco em branco ou o fim da string, acusa um erro de
    // sintaxe e puxa o resto ate encontrar um espaco em branco
    // ou caracter nulo...
    if(**pos != '[' && **pos != ' ' && **pos != 0)
    {
      ret = A_erro; // Tipo = erro...
      while(**pos != ' ' && **pos != 0)
        *buffer++ = *(*pos)++;
    }
    *buffer = 0;
  }
  // ...inteiro:
  // Verifica se o primeiro caracter eh um digito ou um sinal de + ou -...
  else if(**pos == '-' ||
          **pos == '+' || (**pos >= '0' && **pos <= '9'))
  {
    ret = A_inteiro; // Tipo = inteiro...

    // Transfere os bytes para o buffer enquanto for digito...
    do
    {
      *buffer++ = *(*pos)++; // Transfere o byte p/ o buffer...
    } while(**pos >= '0' && **pos <= '9');

    // Se o proximo caracter apos o numero nao for o inicio de
    // um bloco ou espaco em branco ou o fim da string, acusa
    // um erro de sintaxe e puxa o resto da string ate encontrar um espaco
    // em branco ou o caracter nulo...
    if(**pos != '[' && **pos != ' ' && **pos != 0)
    {
      ret = A_erro; // Tipo = erro...
      while(**pos != ' ' && **pos != 0)
        *buffer++ = *(*pos)++;
    }
    *buffer = 0;

    // Verifica se o numero esta dentro da faixa permitida a um numero
    // inteiro...
    if(ret == A_inteiro && (atof(ibuffer) < -32768 || atof(ibuffer) > 32767))
      ret = A_erro; // Tipo = erro...
  }
  // ...bloco de comandos
  // Verifica se o primeiro caracter eh um inicio de bloco...
  else if(**pos == '[')
  {
    int nopen = 0; // Numero de colchetes abertos...

    ret = A_bloco; // Tipo = bloco...

    // Executa o loop ate que seja atingido o final da string,
    // ou nopen == 0...
    do
    {
      if(**pos == '[') nopen++; // Verifica se abriu outro bloco...
      if(**pos == ']') nopen--; // Verifica se fechou um bloco...
      *buffer++ = *(*pos)++;    // Transfere o byte p/ o buffer...
    } while(nopen != 0 && **pos != 0);

    // Se todos os colchetes nao forem fechados, acusa um erro...
    if(nopen != 0)
      ret = A_erro; // Tipo = erro...
    *buffer = 0;    // Encerra o buffer...
  }
  // ...fim da linha!!!
  // Verifica se eh o fim da string (caracter nulo)...
  else if(**pos == 0)
  {
    ret = A_fim; // Tipo = fim...
    *buffer = 0; // Encerra o buffer vazio...
  }
  // ...nenhum dos anteriores, ERRO!!!
  else
  {
    // Puxa toda a string ate encontrar um espaco em branco ou o
    // caracter nulo...
    ret = A_erro; // Tipo = erro...
    while(**pos != ' ' && **pos != 0)
      *buffer++ = *(*pos)++; // Transfere o byte p/ o buffer...
    *buffer = 0; // Encerra o buffer...
  }

  return(ret); // Retorna o tipo identificado...
}

/****************************************************************************
* static int nocmd(char *pos)                                               *
*****************************************************************************
* Verifica se nao ha comandos a serem processados                           *
*****************************************************************************
* Parametros:                                                               *
*   char *pos - A posicao inicial da procura                                *
*****************************************************************************
* Retorna:                                                                  *
*   Um valor indicando se eh o fim da string                                *
****************************************************************************/
static int nocmd(char *pos)
{
  int achou = 0;
  while(*pos != 0)
    if(*pos++ != 32)
    {
      achou = 1;
      break;
    }
  return(!achou);
}

/****************************************************************************
* cmd_stat parse(char *pos)                                                 *
*****************************************************************************
* Interpreta uma string de comandos                                         *
*****************************************************************************
* Parametros:                                                               *
*   char *pos - Endereco do inicio da string de comandos                    *
*****************************************************************************
* Retorna:                                                                  *
*   Um valor cmd_stat indicando o status do ultimo comando que o            *
* interpretador conseguiu executar                                          *
****************************************************************************/
cmd_stat parse(char *pos)
{
  arg_t tipo;                        // Tipo de argumento retornado
  char buffer[MAX_SHELL + 1];        // Buffer temporario
  char args[MAX_ARG][MAX_SHELL + 1]; // Argumentos a serem passados
  char *argp[MAX_ARG];               // Array de ponteiros p/ os parametros
  int ok, cpos, i;                   // Variaveis utilitarias
  cmd_stat stat = S_ok;              // Status de retorno

  // Inicializa os ponteiros a serem passados...
  for(i = 0; i < MAX_ARG; i++)
    argp[i] = (char *) &args[i];

  do
  {
    // Pega o primeiro argumento da linha de comando...
    tipo = nextarg(&pos, buffer);

    // Verifica se eh um comando...
    if(tipo != A_comando)
    {
      if(tipo != A_fim)
      {
        printstr("Comando invalido: ", SERR);
        printline(buffer, SCOD);
        stat = S_erro;
      }
      break;
    }

    // Verifica se o comando existe...
    ok = 0;
    for(i = 0; i < cmdcount; i++)
      if(stricmp(buffer, cmds[i].nome) == 0)
      {
        ok = 1;
        cpos = i;
        break;
      }

    // Se nao encontrar, aborta...
    if(!ok)
    {
      printstr("Comando inexistente: ", SERR);
      printline(buffer, SCOD);
      stat = S_erro;
      break;
    }

    // Pega os argumentos e verifica o tipo...
    ok = 1;
    for(i = 0; i < cmds[cpos].nargs; i++)
    {
      tipo = nextarg(&pos, args[i]); // Captura o proximo argumento...
      if(tipo != cmds[cpos].targs[i]) // Se o tipo nao concordar...
      {
        ok = 0;
        break;
      }
      else if(tipo == A_bloco)
        strrem(args[i]); // Se for um bloco, remove o primeiro e o ultimo chr
    }

    // Se os argumentos concordarem, invoca a funcao tratadora...
    // ... senao, aborta...
    if(ok)
      stat = (*cmds[cpos].func)(argp);
    else
    {
      printstr("Erro de sintaxe: ", SERR);
      if(tipo == A_fim)
        printline("argumento esperado!!!", SMSG);
      else
        printline(args[i], SCOD);
      printstr("Use: ", SMSG);
      printstr(cmds[cpos].nome, SCOD);
      for(i = 0; i < cmds[cpos].nargs; i++)
      {
        switch(cmds[cpos].targs[i])
        {
          case A_comando:
            printstr(" comando", SCOD);
            break;
          case A_inteiro:
            printstr(" inteiro", SCOD);
            break;
          case A_bloco:
            printstr(" [bloco de comandos]", SCOD);
            break;
          case A_erro:
          case A_fim:
        	// casos impossiveis; tratados apenas p/ que o compilador nao acuse warnings
        	break;
        }
      }
      newline();
      printline("Para mais informacoes, digite no prompt: AJUDA nome-do-comando", SINF);
      stat = S_erro;
    }
  } while(stat == S_ok);

  return(stat);
}

/****************************************************************************
* void shell(void)                                                          *
*****************************************************************************
* Fornece um shell para a entrada de comandos pelo usuario                  *
****************************************************************************/
void shell(void)
{
  // Variaveis de controle do historico do shell...
  char hist[MAX_HIST][MAX_SHELL + 1]; // Vetor contendo os comandos digitados
  int histpos = 0, histlen = 0;       // Posicao e tamanho do historico
  int navpos, navlen;                 // Posicao e profundidade da navegacao

  // Variaveis de controle da entrada do usuario...
  char cmds[MAX_SHELL + 1]; // Buffer de entrada
  GrKeyType k;              // Caracter digitado pelo usuario

  // Inicializa o shell...
  parse("tat sobre");

  do
  {
    // Inicia um novo prompt...
    ins = 1;          // Liga insert sempre que iniciar uma nova linha...
    pos = tam = 0;    // Posicao e tamanho zerados...
    navlen = 0;       // Posicao no historico...
    navpos = histpos; // Comando atual do historico...

    // Exibe o prompt...
    printstr(SPROMPT, SFG);

    // Le uma linha de comando...
    do
    {
      // Le uma tecla digitada pelo usuario
      showcursor();
      k = GrKeyRead();
      showcursor();

      // Se for ESC...
      if(k == GrKey_Escape)
        clear();
      // Se for BACKSPACE...
      if(k == GrKey_BackSpace && pos != 0)
      {
        int i;
        rtcurs();
        pos--;
        for(i = pos; i < tam; i++)
          cmds[i] = cmds[i + 1];
        cmds[tam] = 0;
        cmds[tam - 1] = ' ';
        tam--;
        printshell(&cmds[pos], 0, SFG);
      }
      // Se for DELETE...
      if(k == GrKey_Delete && pos != tam)
      {
        int i;
        for(i = pos; i < tam; i++)
          cmds[i] = cmds[i + 1];
        cmds[tam] = 0;
        cmds[tam - 1] = ' ';
        tam--;
        printshell(&cmds[pos], 0, SFG);
      }
      // Se for INSERT...
      if(k == GrKey_Insert)
        ins = !ins;
      // Se for HOME...
      if(k == GrKey_Home)
      {
        int i;
        for(i = pos; i > 0; i--)
          rtcurs();
        pos = 0;
      }
      // Se for END...
      if(k == GrKey_End)
      {
        int i;
        for(i = pos; i < tam; i++)
          avcurs();
        pos = tam;
      }
      // Se for SETA ESQUERDA...
      if(k == GrKey_Left && pos != 0)
      {
        rtcurs();
        pos--;
      }
      // Se for SETA DIREITA...
      if(k == GrKey_Right && pos != tam)
      {
        avcurs();
        pos++;
      }
      // Se for SETA ACIMA...
      if(k == GrKey_Up && navlen < histlen)
      {
        navlen++;
        if(navpos-- == 0)
          navpos = MAX_HIST - 1;
        clear();
        strcpy(cmds, hist[navpos]);
        pos = tam = strlen(cmds);
        printstr(cmds, SFG);
      }
      // Se for SETA ABAIXO...
      if(k == GrKey_Down && navlen > 0)
      {
        navlen--;
        if(navpos++ == MAX_HIST)
          navpos = 0;
        clear();
        strcpy(cmds, navlen == 0 ? "" : hist[navpos]);
        pos = tam = strlen(cmds);
        printstr(cmds, SFG);
      }
      // Se for CTRL + SETA ESQUERDA...
      if(k == GrKey_Control_Left && pos != 0)
      {
        do
        {
          pos--;
          rtcurs();
        } while(pos != 0 && (((cmds[pos] < 'a' || cmds[pos] > 'z') &&
                              (cmds[pos] < 'A' || cmds[pos] > 'Z') &&
                              (cmds[pos] < '0' || cmds[pos] > '9')) ||
                              ((cmds[pos - 1] >= 'a' && cmds[pos - 1] <= 'z') ||
                               (cmds[pos - 1] >= 'A' && cmds[pos - 1] <= 'Z') ||
                               (cmds[pos - 1] >= '0' && cmds[pos - 1] <= '9'))));
      }
      // Se for CTRL + SETA DIREITA...
      if(k == GrKey_Control_Right && pos != tam)
      {
        do
        {
          pos++;
          avcurs();
        } while(pos != tam && (((cmds[pos] < 'a' || cmds[pos] > 'z') &&
                                (cmds[pos] < 'A' || cmds[pos] > 'Z') &&
                                (cmds[pos] < '0' || cmds[pos] > '9')) ||
                                ((cmds[pos - 1] >= 'a' && cmds[pos - 1] <= 'z') ||
                                 (cmds[pos - 1] >= 'A' && cmds[pos - 1] <= 'Z') ||
                                 (cmds[pos - 1] >= '0' && cmds[pos - 1] <= '9'))));
      }
      // Se for um caracter entre 32 e 255, acrescenta o caracter e mostra...
      if(k > 31 && k <= 255 && tam < MAX_SHELL)
      {
        if(ins || pos == tam)
        {
          int i;
          for(i = tam; i > pos; i--)
            cmds[i] = cmds[i - 1];
          cmds[pos] = k;
          pos++;
          tam++;
          cmds[tam] = 0;
          printchar(k, 1, SFG);
          printshell(&cmds[pos], 0, SFG);
        }
        else
        {
          cmds[pos] = k;
          pos++;
          printchar(k, 1, SFG);
        }
      }
    } while(k != GrKey_Return); // Executa ate pressionar [ENTER]...
    cmds[tam] = 0;
    newline();

    // Inclui uma nova entrada no historico do shell
    if(!nocmd(cmds))
    {
      strcpy(hist[histpos++], cmds);
      if(histpos == MAX_HIST)
        histpos = 0;
      if(histlen < MAX_HIST)
        histlen++;
    }
  } while(parse(cmds) != S_fim); // Processa os comandos digitados...
}
